package qs.controls
{
	import mx.core.Container;
	import mx.core.IFactory;
	import flash.geom.Point;
	import qs.utils.AssociativeInstanceCache;
	import mx.core.ClassFactory;
	import mx.core.ScrollPolicy;
	import flash.events.MouseEvent;
	import mx.events.DragEvent;
	import mx.core.IUIComponent;
	import mx.core.DragSource;
	import mx.managers.DragManager;
	import flash.display.PixelSnapping;
	import mx.core.IDataRenderer;
	import flash.display.DisplayObject;
	import flash.events.Event;
	import mx.managers.LayoutManager;
	
	[Event("change")]

	public class FlexibleContainer extends Container
	{
		private var _items:Array = [];
		private var _pendingItems:Array;
		protected var itemsChanged:Boolean = false;
		/** true if the _renderers need to be regenerated */
		protected var _renderersDirty:Boolean = true;
		/** the _renderers representing the data items, one for each item */
		protected var _renderers:Array = [];		
		/** the factory that generates item _renderers
		 */
		private var _itemRendererFactory:IFactory;

		private var _dragTargetIdx:Number;
		private var _dragMouseStart:Point;
		private var _dragMousePos:Point;
		private var _dragPosStart:Point;
		private var _dragAction:String;
		private var _dragImage:UIBitmap;
		private var _renderCache:AssociativeInstanceCache;
				
		protected var animator:LayoutAnimator;
		
		private var _layout : ILayout;
		
		public function FlexibleContainer()
		{
			super();
			_itemRendererFactory= new ClassFactory(CachedLabel);
			animator = new LayoutAnimator();
			animator.updateFunction = layoutUpdated;
			animator.autoPrioritize = false;
			animator.animationSpeed = .2;
			clipContent = true;

			_renderCache = new AssociativeInstanceCache();
			_renderCache.factory = _itemRendererFactory;
			
			addEventListener(MouseEvent.MOUSE_DOWN,dragStart);
			addEventListener(DragEvent.DRAG_ENTER,dragEnter);
			addEventListener(DragEvent.DRAG_OVER,dragOver);
			addEventListener(DragEvent.DRAG_EXIT,dragOut);
			addEventListener(DragEvent.DRAG_DROP,dragDrop);
			addEventListener(DragEvent.DRAG_COMPLETE,dragComplete);
		}

		public var addItems:Function;
		public var removeItem:Function;
		public var moveItems:Function;
		
		//--------------getters and setters---------------------
		
		/** the data source
		 */
		public function set dataProvider(value:Array):void
		{
			_pendingItems= value;

			_renderersDirty = true;
			itemsChanged = true;
			invalidateProperties();			
		}
		public function get dataProvider():Array
		{
			return _items;

		}

		/**
		 * by making the itemRenderer be of type IFactory, 
		 * developers can define it inline using the <Component> tag
		 */
		public function get itemRenderer():IFactory
		{
			return _itemRendererFactory;
		}
		public function set itemRenderer(value:IFactory):void
		{
			_itemRendererFactory = value;
			_renderCache.factory = value;
			_renderersDirty = true;
			invalidateProperties();						
		}
		
		public function get layout() : ILayout
		{
			return _layout;
		}

		public function set layout(value: ILayout) : void
		{
			_layout = value;
			animator.layoutFunction = _layout.generateLayout;

			_layout.attach(this, animator);
			invalidateProperties();
		}
		
		public function get renderers() : Array
		{
			return _renderers;
		}
		
		public function get dragTargetIndex() : Number 
		{
			return _dragTargetIdx;
		}
				
		//----------------overridden methods---------------------

		override protected function commitProperties():void
		{
			// its now safe to switch over new dataProviders.
			if(_pendingItems != null)
			{
				_items = _pendingItems;
				_pendingItems = null;
			}
			
			itemsChanged = false;
			
			if(_renderersDirty)
			{
				// something has forced us to reallocate our _renderers. start by throwing out the old ones.
				_renderersDirty = false;
				for(var i:int=numChildren-1;i>= 0;i--)
					removeChildAt(i);
				
				_renderers = [];
				_renderCache.beginAssociation();
				// allocate new _renderers, assign the data.
				for(i = 0;i<_items.length;i++)
				{
					var renderer:IUIComponent = _renderCache.associate(_items[i]);
					IDataRenderer(renderer).data = _items[i];
					_renderers[i] = renderer;
					addChild(DisplayObject(renderer));
				}
				_renderCache.endAssociation();

			}
			invalidateSize();
		}
		
		override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
		{
			graphics.clear();
			graphics.moveTo(0,0);
			graphics.beginFill(0,0);
			graphics.drawRect(0,0,unscaledWidth,unscaledHeight);
			
			
			animator.invalidateLayout();			
			
			super.updateDisplayList(unscaledWidth,unscaledHeight);
		}
		override public function styleChanged(styleProp:String):void
		{
			invalidateSize();
			invalidateDisplayList();
			animator.invalidateLayout();
		}

		override protected function measure():void
		{
			measuredWidth = 0;
			measuredHeight = 0;
			
			if (_layout)
			{
				var size : Point = _layout.getMeasuredSize();
				measuredWidth = size.x;
				measuredHeight = size.y;
			}
				
			animator.invalidateLayout();		
		}
		
		//----------------public and protected methods---------------------

		public function allowDrag(e:DragEvent,dragAction:String = "move"):void
		{
			_dragAction = dragAction;

			if(e.dragInitiator == this)
			{
		    	DragManager.acceptDragDrop(this);
				e.action = _dragAction;
				DragManager.showFeedback(_dragAction);
			}
			else
			{
		    	DragManager.acceptDragDrop(this);								
				DragManager.showFeedback(_dragAction);
			}
			animator.animationSpeed = .1;
		}

		public function showDragFeedback(e:DragEvent,dragAction:String = "move"):void
		{
			_dragAction = dragAction;
			DragManager.showFeedback(_dragAction);				
			_dragTargetIdx = _layout.findItemAt(mouseX,mouseY,true);
			_dragMousePos = new Point(mouseX,mouseY);
			animator.invalidateLayout(true);						
		}

		//----------------private methods---------------------

		private function layoutUpdated():void
		{
			validateDisplayList();
		}

		private function dragStart(e:MouseEvent):void
		{
			if (_layout.dragStart(e))
				return;
			
			var dragIdx:Number = _layout.findItemAt(mouseX,mouseY,false);
			if(isNaN(dragIdx))
				return;
			
			if(verticalScrollBar != null && verticalScrollBar.contains(DisplayObject(e.target)))
				return;
			if(horizontalScrollBar != null && horizontalScrollBar.contains(DisplayObject(e.target)))
				return;
				
			var dragItem:IUIComponent = _renderers[dragIdx];
			
			if (!dragItem)
				return;
			
			_dragImage = new UIBitmap(dragItem,PixelSnapping.NEVER);			

			var dragSrc:DragSource = new DragSource();
			dragSrc.addData([dataProvider[dragIdx]],"items");
			dragSrc.addData(dragIdx,"index");
			
			_dragMouseStart = new Point(mouseX, mouseY);

			_dragPosStart = DisplayObject(dragItem).localToGlobal(new Point(0,0));
			_dragPosStart = globalToLocal(_dragPosStart);
			DragManager.doDrag(this,dragSrc,e,_dragImage,-_dragPosStart.x ,-_dragPosStart.y,.6);
		}

		private function dragEnter(e:DragEvent):void
		{
			if (_layout.dragEnter(e))
				return;
		}

		private function dragOver(e:DragEvent):void
		{
			if (_layout.dragOver(e))
				return;
		}

		private function dragDrop(e:DragEvent):void
		{
			if (_layout.dragDrop(e))
				return;

			_dragTargetIdx = _layout.findItemAt(mouseX,mouseY,true);
			if(e.dragInitiator == this)
			{
				e.dragSource.addData(this,"target");
			}

			if(e.dragInitiator == this && e.action == _dragAction)
			{				
				DragManager.showFeedback(_dragAction);
				var dragFromIndex:Number = Number(e.dragSource.dataForFormat("index"));

				// First, teleport the item to where the drag proxy is, and 
				// hide the drag proxy.		
				if (e.action != DragManager.COPY)
				{
					var renderer:IUIComponent = _renderers[dragFromIndex];
					var target:LayoutTarget = animator.targetFor(renderer);
					
					renderer.x = target.x = _dragPosStart.x + (mouseX - _dragMouseStart.x);
					renderer.y = target.y = _dragPosStart.y + (mouseY - _dragMouseStart.y);
					_dragImage.visible = false;
				}
				
				if (!isNaN(_dragTargetIdx))
				{
					if(moveItems != null)
					{
						moveItems(e,_dragTargetIdx,dragFromIndex,this);
						_renderersDirty = true;
						invalidateProperties();
					}
					else
					{
						if(_dragTargetIdx > dragFromIndex)
						{
							_items.splice(_dragTargetIdx,0,_items[dragFromIndex]);
							if(e.action != DragManager.COPY)
								_items.splice(dragFromIndex,1);
							setChildIndex(_renderers[dragFromIndex],numChildren-1);
							if(e.action != DragManager.COPY)
							{
								_renderers.splice(_dragTargetIdx,0,_renderers[dragFromIndex]);
								_renderers.splice(dragFromIndex,1);
							}
							else
							{
								_renderers.splice(_dragTargetIdx,0,newRendererFor(_items[_dragTargetIdx]));
							}
						
						}
						else
						{
							var tmp:* = _items[dragFromIndex];
							if(e.action != DragManager.COPY)
								_items.splice(dragFromIndex,1);
							_items.splice(_dragTargetIdx,0,tmp);
							tmp = _renderers[dragFromIndex];
							setChildIndex(tmp,numChildren-1);
							if(e.action != DragManager.COPY)
							{
								_renderers.splice(dragFromIndex,1);
								_renderers.splice(_dragTargetIdx,0,tmp);												
							}
							else
							{
								_renderers.splice(_dragTargetIdx,0,newRendererFor(_items[_dragTargetIdx]));
							}
						}
					}
				}
			}
			else
			{
				if(addItems != null)
				{
					addItems(e,_dragTargetIdx,this);
					_renderersDirty = true;
					invalidateProperties();
				}
				else
				{

					var newItems:Array = (e.dragSource.dataForFormat("items") as Array).concat();
					var new_renderers:Array = [];
					for(var i:int =0;i<newItems.length;i++)
					{
						var r:IUIComponent = new_renderers[i] = newRendererFor(newItems[i]);
						_items.splice(_dragTargetIdx,0,newItems[i]);
						_renderers.splice(_dragTargetIdx+i,0,r);
																					
					}
				}
			}
			dispatchEvent(new Event("change"));
			_dragTargetIdx = NaN;
			animator.animationSpeed = .2;
			animator.invalidateLayout(true);
		}
		
		private function newRendererFor(item:*):IUIComponent
		{
			var r:IUIComponent = _itemRendererFactory.newInstance();
			IDataRenderer(r).data = item;
			addChild(DisplayObject(r));
			return r;
		}
		private function dragComplete(e:DragEvent):void
		{
			if (_layout.dragComplete(e))
				return;
			
			if(e.action == DragManager.MOVE && e.dragSource.dataForFormat("target") != this)
			{
				var dragFromIndex:Number = Number(e.dragSource.dataForFormat("index"));
				if(removeItem != null)
				{
					removeItem(dragFromIndex,this);
					_renderersDirty = true;
					invalidateProperties();
				}
				else
				{
					_items.splice(dragFromIndex,1);
					var r:IUIComponent = _renderers.splice(dragFromIndex,1)[0];
					removeChild(DisplayObject(r));
				}
				animator.invalidateLayout();
				dispatchEvent(new Event("change"));
			}
			invalidateSize();
		}

		private function dragOut(e:DragEvent):void
		{
			if (_layout.dragOut(e))
				return;
			
			_dragTargetIdx = NaN;
			animator.animationSpeed = .2;						
			animator.invalidateLayout(true);
		}


	}

}